'************************************************************************************
' Copyright © 2003-2005 Kathleen Dollard
'
' This software is provided 'as-is', without any express or implied warranty. In no 
' event will the author be held liable for any damages arising from the use of this
' software.
' 
' You may use this software for any purpose, including commercial applications, 
' and you may redistribute this software as part of application delivery: however, 
' this copyright notice may not be removed and you may not otherwise redistribute 
' this software directly other than providing a link to wwww.gendotnet.com. 
'
' You may alter this software to better server your needs (I'd like to hear about your 
' changes in order to make the software better). If you alter the softwre, you must  
' clearly mark it as altered and must not be misrepresented as the original software.
' nor wholly your original work.
'*************************************************************************************
Option Strict On
Option Explicit On 

Imports System

Namespace KADGen.CodeGenerationSupport
   Public Delegate Function GenerateDelegate( _
               ByVal outputFileName As String, _
               ByVal multiPassDirective As CodeGenerationSupport.IMultiPassDirective, _
               ByVal nodeSelect As Xml.XmlNode, _
               ByVal runUtility As CodeGenerationSupport.RunUtility, _
               ByVal statusOutput As CodeGenerationSupport.IStatusOutput) _
               As IO.Stream

   Public Mustinherit Class MultiPass

#Region "Class level declarations"
      Private mSelectXML As Xml.XmlDocument
      Private mSelectXMLFileName As String
      Private mParams() As CodeGenerationSupport.XSLTParam
      Private mnsmgrDriving As Xml.XmlNamespaceManager
      Private mnsMgrMeta As Xml.XmlNamespaceManager
#End Region

      '#Region "Constructors"
      '#End Region

#Region "Protected properties and methods"
      Protected Sub SetParams( _
               ByVal params As CodeGenerationSupport.XSLTParam())
         Me.mParams = params
      End Sub

      Protected ReadOnly Property Params() _
               As CodeGenerationSupport.XSLTParam()
         Get
            Return mParams
         End Get
      End Property

      Protected ReadOnly Property NsMgrMeta() As Xml.XmlNamespaceManager
         Get
            Return Me.mnsMgrMeta
         End Get
      End Property


      Protected Sub MultiPass( _
               ByVal multipassDirective As CodeGenerationSupport.IMultiPassDirective, _
               ByVal xmlMetadata As Xml.XmlDocument, _
               ByVal MetadataFileName As String, _
               ByVal delegGenerate As GenerateDelegate, _
               ByVal statusDirective As CodeGenerationSupport.IStatusDirective, _
               ByVal runUtility As RunUtility)
         Const source As String = "Multipass.Multipass"
         Dim selectFileName As String
         Dim xmlSelect As New Xml.XmlDocument
         Dim nodeList As Xml.XmlNodeList = Nothing
         Dim outputFileName As String
         Dim statusOutput As CodeGenerationSupport.IStatusOutput
         Dim stream As IO.Stream = Nothing
         Dim madeFile As Boolean
         Dim itemstatus As Utility.SourceControlBase.ItemStatus
         Dim doRun As Boolean
         Dim multiPass As CodeGenerationSupport.IMultiPassSupportElement
         Dim outputRule As CodeGenerationSupport.IOutputRuleSupportElement
         multiPass = multipassDirective.MultiPass
         outputRule = multipassDirective.OutputRule
         selectFileName = multiPass.SelectFile.Trim
         If Not selectFileName Is Nothing AndAlso selectFileName.Length > 0 Then
            xmlSelect = OpenMetadataFile(xmlMetadata, MetadataFileName, selectFileName, multipassDirective)
            Try
               nodeList = xmlSelect.SelectNodes(multiPass.SelectXPath, mnsMgrMeta)
            Catch ex As Exception
               statusOutput = statusDirective.AddStatusOutput( _
                        "Select XPath failed", _
                        FileTreatment.NoNewFile)
               statusOutput.Status = OutputStatus.Failure
            End Try
            If Not nodeList Is Nothing Then
               statusDirective.CountSelected = nodeList.Count
               If nodeList.Count = 0 Then
                  statusOutput = statusDirective.AddStatusOutput( _
                           "No Files to Run", _
                           FileTreatment.NoNewFile)
                  statusOutput.Status = OutputStatus.Success
               Else
                  For Each nodeSelect As Xml.XmlNode In nodeList
                     madeFile = False
                     doRun = False
                     Try
                        outputFileName = IO.Path.Combine( _
                                 CodeGenerationSupport.RunUtility.ParseFilenamePattern(multiPass.OutputDir, nodeSelect, mnsMgrMeta), _
                                 CodeGenerationSupport.RunUtility.ParseFilenamePattern(multiPass.OutputFilePattern, nodeSelect, mnsMgrMeta))
                     Catch ex As Exception
                        statusOutput = statusDirective.AddStatusOutput( _
                                 "Failure building filename", _
                                 FileTreatment.NoNewFile)
                        statusOutput.AddStatusFailure(source, _
                                 "Could not build output filename", _
                                 Nothing)
                        Throw
                     End Try
                     statusDirective.CurrentFileName = outputFileName
                     statusOutput = statusDirective.AddStatusOutput( _
                              outputFileName, _
                              FileTreatment.Unknown)
                     If outputRule Is Nothing Then
                        doRun = True
                     ElseIf runUtility.ShouldRun(outputFileName, _
                              outputRule.OutputGenType, _
                              outputRule.HashWhitespace, _
                              outputRule.OutputFileType, statusOutput) Then
                        doRun = True
                     End If

                     If Not doRun Then
                        ' In this case, we don't know why it's still in progress.
                        statusOutput.Status = OutputStatus.Failure
                     Else
                        Try
                           stream = delegGenerate.Invoke(outputFileName, _
                                       multipassDirective, _
                                       nodeSelect, runUtility, _
                                       statusOutput)
                           If Not stream Is Nothing And Not outputRule Is Nothing Then
                              Utility.Tools.MakePathIfNeeded(outputFileName)
                              madeFile = runUtility.MarkWriteAndClose( _
                                       stream, _
                                       multipassDirective.OutputRule.OutputFileType, _
                                       multipassDirective.OutputRule.OutputGenType, _
                                       multipassDirective.OutputRule.HashOutput, _
                                       outputFileName, statusOutput)
                           End If
                           statusDirective.CountCreated += 1
                           statusOutput.Status = statusOutput.ChildStatus
                           If madeFile Then
                              statusDirective.CountOutput += 1
                           End If
                        Catch ex As System.Exception
                           statusOutput.AddStatusException(source, ex.Message, ex)
                        Finally
                           Try
                              If stream IsNot Nothing Then
                                 stream.Close()
                              End If
                           Catch
                           End Try
                        End Try
                     End If
                     statusOutput.Status = statusOutput.ChildStatus
                  Next
               End If
            End If
         End If
      End Sub

      ''' <summary>
      ''' Sets parameters for each call to the template
      ''' </summary>
      ''' <param name="nodeSelect"></param>
      ''' <param name="outputFileName"></param>
      ''' <remarks>
      ''' The order in which things are added is:
      ''' <list>
      ''' <item>The stock parameters are added explictly</item>
      ''' <item>If the attribute exists on the selected node, that attribute value is used</item>
      ''' <item>If the name is the name of an element in the selected nodes namespace, it's Name attribute is added.</item>
      ''' <item>If the attribute exists going up the hierarchy, that attribute value is used</item>
      ''' <item>If the attribute exists anywhere in the document, that attribute value is used</item>
      ''' </list>
      ''' </remarks>
      Protected Sub SetParameters( _
                ByVal nodeSelect As Xml.XmlNode, _
                ByVal outputFileName As String)
         If Not Me.Params Is Nothing Then
            For i As Int32 = 0 To mParams.GetUpperBound(0)
               If mParams(i).Source <> XSLTParamSource.Script Then
                  Select Case mParams(i).Name.ToLower
                     Case "gendatetime"
                        mParams(i).Value = CStr(System.DateTime.Now())
                     Case "filename"
                        mParams(i).Value = outputFileName
                     Case "database"
                        Dim nsmgr As New Xml.XmlNamespaceManager(nodeSelect.OwnerDocument.NameTable)
                        nsmgr.AddNamespace("dbs", nodeSelect.NamespaceURI)
                        Dim nodeTemp As Xml.XmlNode = nodeSelect.SelectSingleNode( _
                                 "ancestor::dbs:DataStructure", nsmgr)
                        mParams(i).Value = Utility.Tools.GetAttributeOrEmpty( _
                                 nodeTemp, "Name")
                     Case "nodeselect"
                        mParams(i).Value = nodeSelect
                     Case Else
                        Dim nodeAttr As Xml.XmlNode
                        Dim nodeTemp As Xml.XmlNode
                        nodeAttr = nodeSelect.Attributes.GetNamedItem(mParams(i).Name)
                        If nodeAttr IsNot Nothing Then
                           mParams(i).Value = nodeAttr.Value
                        Else
                           Dim nsmgr As New Xml.XmlNamespaceManager(nodeSelect.OwnerDocument.NameTable)
                           nsmgr.AddNamespace("xxx", nodeSelect.NamespaceURI)
                           nodeTemp = nodeSelect.SelectSingleNode( _
                                 "ancestor::xxx:" & mParams(i).Name, nsmgr)
                           If nodeTemp IsNot Nothing Then
                              mParams(i).Value = Utility.Tools.GetAttributeOrEmpty( _
                                    nodeTemp, "Name")
                           Else
                              nodeTemp = nodeSelect.SelectSingleNode( _
                                 "ancestor::*/@" & mParams(i).Name)
                              If nodeTemp IsNot Nothing Then
                                 mParams(i).Value = (CType(nodeTemp, Xml.XmlAttribute)).Value
                              Else
                                 ' The order in which items will be found will be indeterminate
                                 nodeTemp = nodeSelect.SelectSingleNode( _
                                    "//*/@" & mParams(i).Name)
                                 If nodeTemp Is Nothing Then
                                    Diagnostics.Debug.WriteLine("Could not find xpath:")
                                 Else
                                    mParams(i).Value = (CType(nodeTemp, Xml.XmlAttribute)).Value
                                 End If
                              End If
                           End If
                        End If
                  End Select
               End If
            Next
         End If
      End Sub

#End Region

#Region "Private properties and methods"
      Private Function OpenMetadataFile( _
               ByVal xmlMetadata As Xml.XmlDocument, _
               ByVal metadataFileName As String, _
               ByVal selectFileName As String, _
               ByVal multiPassDirective As CodeGenerationSupport.IMultiPassDirective) _
               As Xml.XmlDocument
         Dim xmlSelect As Xml.XmlDocument
         If (Not xmlMetadata Is Nothing And _
                  Not metadataFileName Is Nothing) AndAlso _
                  metadataFileName = selectFileName Then
            xmlSelect = xmlMetadata
         Else
            If (Not mSelectXML Is Nothing And _
                        Not mSelectXMLFileName Is Nothing) AndAlso _
                     mSelectXMLFileName = selectFileName Then
               xmlSelect = mSelectXML
            Else
               ' Well, add it
               xmlSelect = New Xml.XmlDocument
               xmlSelect.Load(selectFileName)
               mSelectXML = xmlSelect
               mSelectXMLFileName = selectFileName
               SetNsMgrMeta(xmlSelect, multiPassDirective)

               mnsmgrDriving = Utility.xmlHelpers.BuildNamespacesManagerForRoot( _
                        CType(multiPassDirective, Xml.XmlNode).OwnerDocument)
            End If
         End If
         Return xmlSelect
      End Function

      Private Sub SetNsMgrMeta( _
               ByVal xmlSelect As Xml.XmlDocument, _
               ByVal multiPassDirective As CodeGenerationSupport.IMultiPassDirective)
         mnsMgrMeta = Utility.xmlHelpers.BuildNamespacesManagerForRoot(xmlSelect)
         Dim nsPrefixes() As String = multiPassDirective.MultiPass.SelectNSPrefix.Split(";"c)
         Dim nsNamespaces() As String = multiPassDirective.MultiPass.SelectNamespace.Split(";"c)
         If nsPrefixes.Length <> nsNamespaces.Length Then
            ' TODO: Change this to a status reported message
            Throw New Exception("The number of select namespaces and prefixes in the semi-colon delimted list must match")
         End If
         For i As Int32 = 0 To nsPrefixes.GetUpperBound(0)
            mnsMgrMeta.AddNamespace( _
                     nsPrefixes(i).Trim(), _
                     nsNamespaces(i).Trim())
         Next
      End Sub


#End Region

   End Class
End Namespace